#!/usr/bin/python3
"""

Configure sysconfig files

The sysconfig directory contains a variety of system configuration files.
The values found in each of the configuration files may need to be specified
before first boot.

Currently, only the kernel and network configs can be modified.

Kernel supports the params:
  - update_default(UPDATEDEFAULT) of type boolean
  - default_kernel(DEFAULTKERNEL) of type string

Network supports the params:
  - networking(NETWORKING) of type boolean
  - no_zero_conf(NOZEROCONF) of type boolean

Network-scripts supports the params:
  - ifcfg - this parameter allows creation of 'ifcfg-*' files under
    'network-scripts' subdirectory. 'ifcfg' type is object (dictionary) with
    all of its keys specifying the interface name and resulting in creation
    of configuration files with the same name and prefix 'ifcfg-'.
    - Network interface name constraints are:
      - maximum length of IFNAMSIZ (including the terminating null byte)
      - name accepted by dev_valid_name() function from kernel net/core/dev.c
    - Currently supported subset of configuration options in ifcfg files is:
      - bootproto(BOOTPROTO) of type string with allowed values:
        - none
        - bootp
        - dhcp
        - static
        - ibft
        - autoip
        - shared
      - device(DEVICE) of type string
      - ipv6init(IPV6INIT) of type boolean
      - onboot(ONBOOT) of type boolean
      - peerdns(PEERDNS) of type boolean
      - type(TYPE) of type string with allowed values:
        - Ethernet
        - Wireless
        - InfiniBand
        - Bridge
        - Bond
        - Vlan
      - userctl(USERCTL) of type boolean

This stage will overwrite existing files.

"""

import sys
import os

import osbuild.api


SCHEMA = r"""
"additionalProperties": false,
"properties": {
  "kernel": {
    "additionalProperties": false,
    "type": "object",
    "description": "sysconfig kernel options",
    "properties": {
      "update_default": {
        "type": "boolean",
        "description": "This option makes a newly installed kernel as the default in the boot entry selection."
      },
      "default_kernel": {
        "type": "string",
        "description": "This option specifies what package type will be used as the default."
      }
    }
  },
  "network": {
    "additionalProperties": false,
    "type": "object",
    "description": "sysconfig network options",
    "properties": {
      "networking": {
        "type": "boolean",
        "description": "Enables or disables networking"
      },
      "no_zero_conf": {
        "type": "boolean",
        "description": "Disables the zero configuration network suite"
      }
    }
  },
  "network-scripts": {
    "additionalProperties": false,
    "type": "object",
    "description": "sysconfig network-scripts options",
    "properties": {
      "ifcfg": {
        "additionalProperties": false,
        "type": "object",
        "description": "Keys are interface names, values are objects containing interface configuration.",
        "patternProperties": {
        "^[^/:.\\s]{1,2}[^/:\\s]{0,13}$": {
          "additionalProperties": false,
          "type": "object",
          "properties": {
            "bootproto": {
              "type": "string",
              "enum": [
                "none",
                "bootp",
                "dhcp",
                "static",
                "ibft",
                "autoip",
                "shared"
              ],
              "description": "Method used for IPv4 protocol configuration."
            },
            "device": {
              "type": "string",
              "description": "Interface name of the device."
            },
            "ipv6init": {
              "type": "boolean",
              "description": "Whether to initialize this device for IPv6 addressing."
            },
            "onboot": {
              "type": "boolean",
              "description": "Whether the connection should be autoconnected."
            },
            "peerdns": {
              "type": "boolean",
              "description": "Whether to modify /etc/resolv.conf."
            },
            "type": {
              "type": "string",
              "enum": [
                "Ethernet",
                "Wireless",
                "InfiniBand",
                "Bridge",
                "Bond",
                "Vlan"
              ],
              "description": "Base type of the connection."
            },
            "userctl": {
              "type": "boolean",
              "description": "Whether non-root users are allowed to control the device."
            }
          }
          }
        }
      }
    }
  }
}
"""


# sysconfig uses yes and no instead of true and false
def bool_to_string(value):
    return "yes" if value else "no"


def configure_kernel(tree, kernel_options):
    if not kernel_options:
        return

    with open(f"{tree}/etc/sysconfig/kernel", 'w') as kernel_file:
        for option, value in kernel_options.items():
            if option == "update_default":
                kernel_file.write(f"UPDATEDEFAULT={bool_to_string(value)}\n")
            elif option == "default_kernel":
                kernel_file.write(f"DEFAULTKERNEL={value}\n")
            else:
                # schema does not currently allow any additional properties but it may at some point
                raise ValueError(f"Error: unknown property {option} specified for sysconfig kernel config.")


def configure_network(tree, network_options):
    if not network_options:
        return

    with open(f"{tree}/etc/sysconfig/network", 'w') as network_file:
        for option, value in network_options.items():
            if option == "networking":
                network_file.write(f"NETWORKING={bool_to_string(value)}\n")
            elif option == "no_zero_conf":
                network_file.write(f"NOZEROCONF={bool_to_string(value)}\n")
            else:
                # schema does not currently allow any additional properties but it may at some point
                raise ValueError(f"Error: unknown property {option} specified for sysconfig network config.")


def configure_network_scripts(tree, network_scripts_options):
    if not network_scripts_options:
        return

    # if the network-scripts dir does not yet exist create it
    os.makedirs(f"{tree}/etc/sysconfig/network-scripts", exist_ok=True)

    network_scripts_ifcfg_options = network_scripts_options.get("ifcfg", {})

    configure_network_scripts_ifcfg(tree, network_scripts_ifcfg_options)


def configure_network_scripts_ifcfg(tree, network_scripts_ifcfg_options):
    for ifname, ifconfig in network_scripts_ifcfg_options.items():
        lines = []
        for option, value in ifconfig.items():
            if option == "device":
                lines.append(f'DEVICE="{value}"\n')
            elif option == "bootproto":
                lines.append(f'BOOTPROTO="{value}"\n')
            elif option == "onboot":
                lines.append(f'ONBOOT="{bool_to_string(value)}"\n')
            elif option == "type":
                lines.append(f'TYPE="{value}"\n')
            elif option == "userctl":
                lines.append(f'USERCTL="{bool_to_string(value)}"\n')
            elif option == "peerdns":
                lines.append(f'PEERDNS="{bool_to_string(value)}"\n')
            elif option == "ipv6init":
                lines.append(f'IPV6INIT="{bool_to_string(value)}"\n')
            else:
                # schema does not currently allow any additional properties but it may at some point
                raise ValueError(f"Error: unknown property {option} specified for sysconfig network-scripts/ifcfg "
                    "config.")

        if lines:
            with open(f"{tree}/etc/sysconfig/network-scripts/ifcfg-{ifname}", 'w') as ifcfg_file:
                ifcfg_file.writelines(lines)


def main(tree, options):
    # if the sysconfig dir does not yet exist create it
    os.makedirs(f"{tree}/etc/sysconfig/", exist_ok=True)

    kernel_options = options.get("kernel", {})
    network_options = options.get("network", {})
    network_scripts_options = options.get("network-scripts", {})

    configure_kernel(tree, kernel_options)
    configure_network(tree, network_options)
    configure_network_scripts(tree, network_scripts_options)

    return 0


if __name__ == '__main__':
    args = osbuild.api.arguments()
    r = main(args["tree"], args["options"])
    sys.exit(r)
